feat: add noInterruptOnUserMessage and noContinueWhileChildrenActive options - #53
Conversation
…ad of pausing) When enabled, a new human message no longer pauses an active goal with stopReason 'user intervention'. The goal loop keeps running and the message steers the next continuation, matching Codex-style steering. Gates the three pause sites (chat.message, auto-continue claim guard, and the idle continuation driver); plugin-owned command/continuation messages are never interventions either way. Default remains false. Adds option docs to README, index.d.ts, and CHANGELOG, plus tests: a chat.message + idle steering test and a normalizeOptions boolean test.
|
Hi @willytop8 — this PR now adds two goal-loop options (defaults unchanged): noInterruptOnUserMessage (human messages steer instead of pausing the goal) and noContinueWhileChildrenActive (defer auto-continue while subagent/background children are active). CI runs are still waiting on workflow approval (action_required) — could you approve them to start the pipeline? The full CI matrix (npm run check on Node 18/20/22/24) passes locally at the final commit (356/356), plus coverage, type contracts, and smoke. Open to review feedback on either option. |
…hile subagents run) When enabled, auto-continue is deferred while the session has active child sessions (subagents, background tasks): the goal stays running but the goal loop does not prompt the orchestrator over work a child is already doing. The gate lives in claimContinuationSource, which covers both continuation paths (normal continue and budget wrapup). It checks opencode's children and status endpoints through the existing shape adapter (children/status are replay-safe read-only operations); hosts that cannot report children/status fail open and continue as before. Default remains false. Adds adapter coverage (children/status shape probing) and behavioral tests (deferral while a child is busy, continuation once children are idle, and unchanged default behavior).
…age patch) Pin the willytop8 goal-plugin fork at 03c493c (feat/no-interrupt-user-message): a no-interrupt config option so human messages steer an active goal instead of pausing it. PR: willytop8/OpenCode-goal-plugin#53.
The "mutating SDK calls are never replayed" mutant pins the literal contents of REPLAY_SAFE_OPERATIONS, so widening the set to include the read-only children/status operations left the anchor matching nothing and the contract asserted before running the mutant. Retarget the anchor at the current literal; the mutant is still killed, so the guarantee that a mutating operation cannot be added to the replay-safe set is unchanged.
|
Thanks for approving the workflows and for the mutation-anchor fix (0cfe9bb) — all checks are green now (Node 18/20/22/24, filesystem matrix, CodeQL, and quality contracts). The PR is mergeable as-is. Is there anything else you'd like changed before review/merge? Happy to adjust option naming, docs, or behavior. |
|
Thanks — taking this, with one fix that has to go in before I ran it against a live opencode 1.18.14 server, driving a real child session to The parent emits nothing across the child's entire lifecycle. Since the continuation driver is Fix is to remember the children a deferral was taken on and re-drive the parent when one of them goes Getting that right took more than I expected — the wake mechanism turned out to need a fair amount of Six smaller things going in alongside it:
I also drove the So Which leaves the gate in an awkward spot: for synchronous children it's dead code, and for asynchronous CI is green on this now. One thing I did on your branch: our |
What changed
Adds two plugin-level options (both default
false, so existing behavior is unchanged):noInterruptOnUserMessage— whentrue, a new human message no longer pauses an active goal withstopReason: "user intervention":chat.messagehook skips the immediate pause/abort;newHumanMessage/userInterventionDetected;userInterventionDetected.The goal loop keeps running and the human message is included in the next continuation, so the message steers the loop (Codex-style) instead of stopping for
/goal resume. Plugin-owned command and continuation messages were already excluded from intervention detection and remain so.noContinueWhileChildrenActive— whentrue, auto-continue is deferred while the session has active child sessions (subagents, background tasks): the goal stays running, but the goal loop does not prompt the orchestrator over work a child is already doing. The gate lives inclaimContinuationSource, which covers both continuation paths (normal continue and budget wrapup), and checks opencode'schildren+statusendpoints through the existing shape adapter (children/statusare replay-safe read-only operations). Hosts that cannot report children/status fail open and continue as before.Why
For long-running autonomous goal loops, any user-role message currently stops the goal and requires an explicit
/goal resume. That includes messages that are not real human steering, e.g. synthetic background-task result messages injected by some hosts, which can pause a goal seconds after it starts (observed in practice: a goal paused 55s in by a background-task completion notification). With the option enabled, such messages leave the loop untouched while real user input steers the next continuation.In particular, OpenCode's experimental background subagents may interrupt an active goal: when a background-task result is delivered into the session as a synthetic user-role message (
type: text,synthetic: true, no plugin metadata), the currentchat.messagepath treats it as human intervention and pauses the goal. WithnoInterruptOnUserMessage: truethose synthetic results no longer pause the loop; real human messages keep steering the next continuation instead of stopping the goal.Conversely, while a background subagent is still running, the goal loop currently has no way to know it: the plugin's auto-continue fires on the orchestrator's own idle even though a child is doing the work, risking overlapping edits on the same files.
noContinueWhileChildrenActive: truedefers the continuation until the children are idle (matching the guard omo-slim applies to its own continuation nudge).Checks run
node-compatibility(npm run check) — passed locally on official Node 18.20.8, 20.20.2, 22.23.2, and 24.19.0 at the final commit (356/356 tests on each).quality-contracts:npm run test:coverage— passed (Node 22)npm run type:check— passed (NodeNext + Bundler)npm run smoke— passednpm run checkon official Node 22 — 356/356 pass (the four new tests included)npm run test:mutation,npm run benchmark:behavior,npm run smoke:packed-host,npm run smoke:packed-tools,npm audit --omit=dev --audit-level=high,npm run pack:check— not completed locally (mutation alone exceeds 15 minutes on this machine and the run was interrupted); these are exercised by the GitHub pipeline once the workflow run is approved.test/goal-plugin.test.js):noInterruptOnUserMessage:true keeps the goal running and steers the loop(chat.message + idle driver)normalizeOptions defaults noInterruptOnUserMessage to false and keeps it booleannoContinueWhileChildrenActive:true defers continuation while a child is active(defers, then continues once the child is idle)active children do not block auto-continue by defaultnormalizeOptions defaults noContinueWhileChildrenActive to false and keeps it booleantest/opencode-session-api.test.js):children/statusshape-adapter and replay-safety coverage.test/persistence-lease.test.jsandtest/session-concurrency.test.jsfail independently of this change (reproduced identically on pristinemain); both pass on the official Node builds used by CI.Manual OpenCode smoke testing
None against a live OpenCode host in this environment; the hook-level behavior is covered by the tests above.
Config